/*****************************************************************************

  The following code is derived, directly or indirectly, from the SystemC
  source code Copyright (c) 1996-2006 by all Contributors.
  All Rights reserved.

  The contents of this file are subject to the restrictions and limitations
  set forth in the SystemC Open Source License Version 2.4 (the "License");
  You may not use this file except in compliance with such restrictions and
  limitations. You may obtain instructions on how to receive a copy of the
  License at http://www.systemc.org/. Software distributed by Contributors
  under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
  ANY KIND, either express or implied. See the License for the specific
  language governing rights and limitations under the License.

 *****************************************************************************/

/*****************************************************************************

  switch.cpp - This is the implementation file for the asynchronous process
               "lpu".

  Original Author: Xinming Chen

 *****************************************************************************/

/*****************************************************************************

  MODIFICATION LOG - modifiers, enter your name, affiliation, date and
  changes you are making here.

      Name, Affiliation, Date:
  Description of Modification:

 *****************************************************************************/

#include <netinet/ether.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include "systemc.h"
#include "pkt.h"
//#include "fifo.h"
#include "inport.h"
#include <queue>   // for std::queue
#include <map>
#include "time.h"

/* ethernet headers are always exactly 14 bytes */
#define SIZE_ETHERNET 14

void inport :: entry()
{
    pkt send_pkt, receive_pkt, SWsend_pkt, SWreceive_pkt;
//variables used in processing;
    pkt ingress_pkt, ingress_frag_pkt;
    pkt egress_pkt, egress_frag_pkt;
    csix_base_hdr ingress_base_hdr;
    csix_base_hdr *egress_base_hdr;
    csix_l2_hdr ingress_l2_hdr;
    csix_l2_hdr *egress_l2_hdr;
    unsigned int frag_offset, frag_length;
    unsigned int receive_bits, send_bits, SWreceive_bytes, SWsend_bytes;
    unsigned char receive_data, RX_data, SWRX_data;
    sc_uint<1> TX_data;
    sc_uint<1> SOF;
    sc_uint<8> SWTX_data;
    bool SWSOF;
    bool send_ready, SWsend_ready;
    std::queue<pkt> q_in_ingress;
    std::queue<pkt> q_out_ingress;
    std::queue<pkt> q_in_egress;
    std::queue<pkt> q_out_egress;
    std::map<unsigned short, pkt> map_egress;
    std::map<unsigned short, unsigned short> map_offset_egress;
    unsigned short frag_ID = 0;
    receive_bits = 0;
    send_bits = 0;
    SWreceive_bytes = 0;
    SWsend_bytes = 0;
//    set ready bits
    send_ready = true;
    SWsend_ready = true;
    SWTXClk.write(0);
    TXClk.write(0);
    SWTXSOF.write(0);
    TXSOF.write(0);
    srand((unsigned)time(NULL));

    while(true)
    {
        if((bool)RXClk && RXClk.event())
        {
            RX_data = (unsigned char)RX.read();
            receive_data = (receive_data << 1) | RX_data;

            if(receive_bits % 8 == 7)
            {
                receive_pkt.data[receive_bits / 8] = receive_data;
                // cout << name() << " received one byte from sender"<< endl;
            }
            else if(receive_bits % 8 == 0)
            {

                SOF = RXSOF.read();

                if((bool)SOF && receive_bits > 7)
                {
                    //cout<<"start of frame!"<<endl;
                    receive_pkt.pkt_length = receive_bits / 8 ;
                    receive_bits = 0;
                    /*temp_outport = getOutport(receive_pkt.data);

                    if(temp_outport >= 0)
                    {
                        receive_pkt.outport = temp_outport;
                        out.write(receive_pkt);
                    }
                    else
                    {
                        cout << "Decoder error! Drop Packet in " << name() << endl;
                    }
                    */
                    //put fragmented packet in q_in_ingress
                    q_in_ingress.push(receive_pkt);
                    //cout << name() << " received one packet from sender, length=" << receive_pkt.pkt_length << endl;
                }
            }

            receive_bits += 1;
        }

        if(InSWTXClk.event())
        {
            if((bool)InSWTXClk)
            {
                if(SWsend_ready && !q_out_ingress.empty())
                {
                    SWsend_pkt = q_out_ingress.front();
                    q_out_ingress.pop();
                    SWsend_ready = false;
                    SWsend_bytes = 0;
                }

                if(!SWsend_ready && SWsend_bytes < SWsend_pkt.pkt_length)
                {

                    SWTXClk.write(InSWTXClk);

                    if((bool)InSWTXClk)
                    {
                        SWTX_data = (sc_uint<8>)(SWsend_pkt.data[SWsend_bytes]);
                        SWTX.write(SWTX_data);
                        SWTXSOF.write(!SWsend_bytes);
                        SWsend_bytes += 1;
                    }
                }
                else if(!SWsend_ready && SWsend_bytes >= SWsend_pkt.pkt_length)//send finished
                {
                    //cout << name() << " sent one packet to SW, length=" << SWsend_bytes << endl;
                    SWsend_bytes = 0;
                    SWsend_ready = true;
                }
            }
            else
            {
                SWTXClk.write(InSWTXClk);
            }
        }

        if((bool)SWRXClk && SWRXClk.event())
        {
//                cout<<"SWreceive_bytes="<<SWreceive_bytes<<endl;
            SWRX_data = (unsigned char)SWRX.read();
            SWSOF = SWRXSOF.read();

            if(SWSOF && SWreceive_bytes > 2)
            {
                SWreceive_pkt.pkt_length = SWreceive_bytes ;
                SWreceive_bytes = 0;

                //output packet to receiver
                q_in_egress.push(SWreceive_pkt);
                //send_pkt = SWreceive_pkt;
                //send_ready = false;
            }

            SWreceive_pkt.data[SWreceive_bytes] = SWRX_data;
            SWreceive_bytes += 1;
        }

        if(InTXClk.event())
        {
            if((bool)InTXClk)
            {
                if(send_ready && !q_out_egress.empty())
                {
                    send_pkt = q_out_egress.front();
                    q_out_egress.pop();
                    send_ready = false;
                    send_bits = 0;
                }

                if(!send_ready && (send_bits / 8 < send_pkt.pkt_length))
                {

                    TXClk.write(InTXClk);

                    if((bool)InTXClk)
                    {
                        TX_data = (sc_uint<1>)((send_pkt.data[send_bits / 8] >> (7 - send_bits % 8)) & 0b00000001);
                        TX.write(TX_data);
                        TXSOF.write(!send_bits);
                        send_bits += 1;
                    }
                }
                else  //send finished
                {
                    send_bits = 0;
                    send_ready = true;
                }
            }
            else
            {
                TXClk.write(InTXClk);
            }
        }

        if(ProcessClk.event())
        {
            //process ingress traffic
            if(!q_in_ingress.empty())
            {
                ingress_pkt = q_in_ingress.front();
                q_in_ingress.pop();
                ingress_l2_hdr.dst_port = getOutport(ingress_pkt.data);

                if(ingress_l2_hdr.dst_port < 0)
                {
                    cout << "Decoder error! Drop Packet in " << name() << endl;
                }
                else
                {
                    //fragment and send to output queue
                    frag_offset = 0;
                    ingress_l2_hdr.frag_ID = frag_ID;
                    ingress_l2_hdr.total_length = ingress_pkt.pkt_length;
                    ingress_l2_hdr.dst_port = rand() % PORTNUM;

                    while(frag_offset < ingress_pkt.pkt_length)
                    {
                        memcpy(ingress_frag_pkt.data, (unsigned char *)&ingress_base_hdr, sizeof(csix_base_hdr));
                        frag_length = ingress_pkt.pkt_length - frag_offset;

                        if(frag_length >= 256 - sizeof(csix_base_hdr) - sizeof(csix_l2_hdr))
                        { frag_length = 256 - sizeof(csix_base_hdr) - sizeof(csix_l2_hdr); }

                        ingress_l2_hdr.frag_offset = frag_offset;
                        ingress_l2_hdr.frag_length = frag_length;
                        memcpy(ingress_frag_pkt.data + sizeof(csix_base_hdr), &ingress_l2_hdr, sizeof(csix_l2_hdr));
                        memcpy(ingress_frag_pkt.data + sizeof(csix_base_hdr) + sizeof(csix_l2_hdr), ingress_pkt.data + frag_offset, frag_length);
                        frag_offset += frag_length;
                        ingress_frag_pkt.pkt_length = frag_length + sizeof(csix_base_hdr) + sizeof(csix_l2_hdr);
                        q_out_ingress.push(ingress_frag_pkt);
                    }

                    frag_ID += 1;
                }
            }

            //process egress traffic
            if(!q_in_egress.empty())
            {
                egress_frag_pkt = q_in_egress.front();
                q_in_egress.pop();
                egress_base_hdr = (csix_base_hdr *)egress_frag_pkt.data;
                egress_l2_hdr = (csix_l2_hdr *)(egress_frag_pkt.data + sizeof(csix_base_hdr));

                if(map_egress.count(egress_l2_hdr->frag_ID) == 0)
                {
                    map_egress[egress_l2_hdr->frag_ID] = egress_pkt;
                    map_egress[egress_l2_hdr->frag_ID].pkt_length = egress_l2_hdr->total_length;
                    map_offset_egress[egress_l2_hdr->frag_ID] = 0;
                }

                memcpy(map_egress[egress_l2_hdr->frag_ID].data + map_offset_egress[egress_l2_hdr->frag_ID], egress_frag_pkt.data + sizeof(csix_base_hdr) + sizeof(csix_l2_hdr), egress_l2_hdr->frag_length);
                map_offset_egress[egress_l2_hdr->frag_ID] += egress_l2_hdr->frag_length;

                if(map_offset_egress[egress_l2_hdr->frag_ID] >= egress_l2_hdr->total_length)
                {
                    //cout << name() << " finished one packet assembly, length=" << map_egress[egress_l2_hdr->frag_ID].pkt_length << endl;
                    q_out_egress.push(map_egress[egress_l2_hdr->frag_ID]);
                    map_egress.erase(egress_l2_hdr->frag_ID);
                    map_offset_egress.erase(egress_l2_hdr->frag_ID);
                }
            }
        }

        wait();
    }
}

int inport ::getOutport(unsigned char *pktData)
{

    const struct ethhdr *ethernet; /* The ethernet header */
    const struct iphdr *ip; /* The IP header */
    const struct tcphdr *tcp; /* The TCP header */
    const u_char *payload; /* Packet payload */
    u_int32_t daddr, netmask;
    u_int size_ip;
    u_int size_tcp;
    int i;
//extract the Link layer, Network layer and Transport Layer
    ethernet = (struct ethhdr *)(pktData);
    ip = (struct iphdr *)(pktData + SIZE_ETHERNET);
    size_ip = (ip->ihl) * 4;

    if(size_ip < 20)
    {
        cout << "   * Invalid IP header length: " << size_ip << " bytes" << endl;
        return -1;
    }

    switch(ip->protocol)
    {
        case 1://"ICMP";
            return -1;
            break;

        case 2://"IGMP";
            return -1;
            break;

        case 6://"TCP";
            tcp = (struct tcphdr *)(pktData + SIZE_ETHERNET + size_ip);
            size_tcp = (tcp->doff) * 4;

            if(size_tcp < 20)
            {
                cout << "   * Invalid TCP header length: " << size_tcp << " bytes" << endl;
                return -1;
            }

            payload = (u_char *)(pktData + SIZE_ETHERNET + size_ip + size_tcp);
            break;

        case 17://"UDP";
            break;

        default:
            return -1;
    }


    for(i = 0; i < routing_entry_num; i++)
    {
//check for outport  by dest IP
        daddr = ip->daddr;
        netmask = 0xFFFFFFFF;
        //note that daddr is big-endian, so netmask should be right-shifted
        netmask >>= 32 - routing_table[i].prefix;
        daddr &= netmask;

        if(daddr == routing_table[i].addr)
        {
            return routing_table[i].outport;
        }
    }

    //printf("Src IP: %s:%u, ", inet_ntoa(saddr), ntohs(tcp->source));
    //printf("Dst IP: %s:%u\n",inet_ntoa(daddr),ntohs(tcp->dest));
    return    routing_table[routing_entry_num - 1].outport;
}

